package com.tender.desensitization;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.tender.common.Result;
import com.tender.properties.AppConfigProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.CollectionUtils;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import com.tender.desensitization.converters.EncryptionConverter;
import com.tender.desensitization.factory.instance.TypeProcessFactoryFacade;
import com.tender.desensitization.factory.field.FieldAnnotationDetectionFactoryFacade;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

import com.tender.desensitization.factory.instance.TypeProcessFactory;
import com.tender.utils.SecretContextUtil;
import com.tender.utils.AESUtil;
import com.tender.utils.RSAUtil;
import com.tender.desensitization.req.SecretRequest;
import com.tender.desensitization.factory.field.FieldAnnotationDetectionFactory;
import com.tender.desensitization.factory.instance.UnSensitiveUserWrapperProcessFactory;
import com.tender.desensitization.factory.field.FieldSensitiveStringDetectionFactory;
import com.tender.desensitization.factory.field.TopClassContext;
import com.tender.exceptions.CommonException;
import com.tender.enums.ErrorTypeEnum;

import static com.tender.desensitization.GlobalRequestResponseAdvice.circleMap;

@SuppressWarnings("all")
@Slf4j
public abstract class SensitiveResponseBodyAdvice extends RequestBodyAdviceAdapter implements ResponseBodyAdvice<Object>, DefaultAdvice, InitializingBean {

    @Autowired
    private AppConfigProperties configProperties;

    @Autowired
    private EncryptionConverter encryptionConverter;

    private final Base64.Encoder base64Encoder = Base64.getEncoder();

    private final Base64.Decoder base64Decoder = Base64.getDecoder();

    private final Map<Class<?>, Boolean> unsensitiveMap = new ConcurrentHashMap<>();

    private final TypeProcessFactoryFacade unsensitiveFactoryCollection = new TypeProcessFactoryFacade();

    private final FieldAnnotationDetectionFactoryFacade sensitiveFieldFactoryCollection = new FieldAnnotationDetectionFactoryFacade();

    /**
     * 判断类型是否是集合类型，如果是集合类型，直接不处理，返回 true
     */
    private final Function<Class<?>, Boolean> function = clazz ->
            List.class.isAssignableFrom(clazz)
                    || Map.class.isAssignableFrom(clazz)
                    || Set.class.isAssignableFrom(clazz);

    public Function<Class<?>, Boolean> getFunction() {
        return function;
    }

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 过滤所有类型的返回值
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  ServerHttpRequest request, ServerHttpResponse response) {
        if (null == body) return null;
        if (!ClassUtils.isPrimitiveOrWrapper(body.getClass())
                && Result.class.isAssignableFrom(body.getClass())) {
            // 只处理 ActionResult 包装类的返回值类型
            Result result = (Result) body;
            Object data = result.getData(); // 原始数据
            if (null != data) {
                // 全局脱敏
                if (needUnsensitiveProcess(data)) {
                    TypeProcessFactory processFactory = unsensitiveFactoryCollection.getProcessFactory(data.getClass());
                    if (null != processFactory) {
                        processFactory.processType(data);
                    }
                }

                // 加密标识
                // 返回结果，整体加密
                if (responseParamNeedEncryption(returnType)) {
                    Result<Object> appResult = Result.success(encryptionConverter.encryptionAllResponse(data));
                    appResult.setEncrypted(true);

                    return appResult;
                }

                // 字段级别加密(对象已经存在)
                if (hasFieldEncryptionAnnotation(data)) {
                    Result<Object> appResult;
                    try {
                        appResult = Result.success(encryptionConverter.wrapResponseData(doFieldEncryption(data)));
                        if (null != appResult.getData()) {
                            appResult.setEncrypted(true);
                            return appResult;
                        }
                    } finally {
                        SecretContextUtil.clear();
                    }
                }
            }
        }
        return body;
    }

    /**
     * request 参数拦截判断(是否需要解密)
     */
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 优先参数都解密 然后是字段级别的解密
        return requestParamNeedDecryption(methodParameter) || hasFieldDecryptionAnnotation(methodParameter);
    }

    protected abstract boolean requestParamNeedDecryption(MethodParameter methodParameter);

    protected abstract boolean responseParamNeedEncryption(MethodParameter methodParameter);

    /**
     * 解密操作
     */
    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        byte[] bytes = new byte[inputMessage.getBody().available()];
        inputMessage.getBody().read(bytes);
        String encrypt = new String(bytes);
        if (StringUtils.isNotBlank(encrypt)) {
            SecretRequest secretRequest = JSON.parseObject(encrypt, SecretRequest.class);
            if (configProperties.isParamTimeCheck()) {
                long now = System.currentTimeMillis();
                Long time = secretRequest.getTime();
                if (null == time) {
                    throw new CommonException(ErrorTypeEnum.REQUEST_PARAM_TIME_NOT_EXISTS);
                }
                if (Math.abs(now - time) > configProperties.getTimeGap()) {
                    throw new CommonException(ErrorTypeEnum.REQUEST_PARAM_EXPIRED);
                }
            }
            if (!configProperties.isDevEnv() && StringUtils.isBlank(secretRequest.getSecretAES128())) {
                throw new CommonException(ErrorTypeEnum.KEY_AES_128_NOT_EXISTS);
            }
            if (!configProperties.isDevEnv() && configProperties.isUseIvAES()) {
                if (StringUtils.isBlank(secretRequest.getIvAES())) {
                    throw new CommonException(ErrorTypeEnum.AES_IV_NOT_EXISTS);
                }
            }

            String keyAES128 = null;
            try {
                if (configProperties.isDevEnv()) {
                    keyAES128 = secretRequest.getKeyAES128();
                } else {
                    keyAES128 = RSAUtil.decrypt(secretRequest.getSecretAES128(), configProperties.getPrivateKeyStr());
                }
            } catch (Exception e) {
                throw new CommonException(ErrorTypeEnum.KEY_AES_128_DECRYPT_ERROR);
            }

            String decrypt = null;
            // 全局参数解密 和 字段解密同时存在，则以全局解密优先（前后端商议好原则）
            if (requestParamNeedDecryption(parameter)) {
                if (configProperties.isUseIvAES()) {
                    decrypt = AESUtil.decrypt(keyAES128, secretRequest.getData().toString(), base64Decoder.decode(secretRequest.getIvAES()));
                } else {
                    decrypt = AESUtil.decrypt(keyAES128, secretRequest.getData().toString(), null);
                }
                log.info("================ 全局解密后的内容：================ {}", decrypt);
            } else {
                // 字段级别的解密
                try {
                    Object requestParam = JSON.parseObject(JSON.toJSONString(secretRequest.getData()), targetType);
                    SecretContextUtil.SecretContext secretContext = new SecretContextUtil.SecretContext();
                    secretContext.setKeyAES128(keyAES128);
                    secretContext.setIvAES(secretRequest.getIvAES());
                    SecretContextUtil.setSecretContext(secretContext);
                    decrypt = JSON.toJSONString(doFieldDecryption(requestParam, keyAES128));
                } finally {
                    SecretContextUtil.clear();
                }
            }

            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decrypt.getBytes());
            return new HttpInputMessage() {
                @Override
                public InputStream getBody() throws IOException {
                    return byteArrayInputStream;
                }

                @Override
                public HttpHeaders getHeaders() {
                    return inputMessage.getHeaders();
                }
            };
        }

        return super.beforeBodyRead(inputMessage, parameter, targetType, converterType);
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return super.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
    }

    private boolean needUnsensitiveProcess(Object data) {
        Class<?> clazz = data.getClass();

        // 针对集合类型
        // TODO 泛型被擦除了
        if (function.apply(clazz)) {
            // clazz = FieldReflectUtils.getGenericClassWithInCollection(clazz);
            // 直接放行，让后续逻辑尝试脱敏处理
            return true;
        }

        if (null == clazz || ClassUtils.isPrimitiveOrWrapper(clazz) || String.class == clazz) {
            return false;
        }

        Boolean flag = unsensitiveMap.get(clazz);
        if (null != flag) {
            return flag;
        }
        synchronized (clazz) {
            try {
                List<Field> allFields = FieldReflectUtils.getAllFields(clazz);

                // 顶级 Class<?>
                if (!CollectionUtils.isEmpty(allFields)) {
                    List<Class<?>> list = Lists.newArrayList(clazz);
                    // 顶级 Class<?>
                    circleMap.put(clazz, list);
                    TopClassContext.setTopClass(clazz);
                }

                for (Field field : allFields) {
                    FieldAnnotationDetectionFactory processFactory = sensitiveFieldFactoryCollection.getProcessFactory(field.getType());
                    if (null != processFactory) {
                        if (processFactory.processType(field)) {
                            unsensitiveMap.put(clazz, true); // 加入缓存
                            return true;
                        }
                    }
                }

                unsensitiveMap.put(clazz, false); // 加入缓存
            } finally {
                circleMap.remove(clazz);              // 清理顶级 Class<?>
                TopClassContext.clear();
            }
        }
        return false;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        unsensitiveFactoryCollection.setTypeProcessFactory(new UnSensitiveUserWrapperProcessFactory(unsensitiveFactoryCollection));
        sensitiveFieldFactoryCollection.setTypeProcessFactory(new FieldSensitiveStringDetectionFactory(sensitiveFieldFactoryCollection));
    }

}
